package servers;

import java.net.MalformedURLException;
import java.net.UnknownHostException;
import java.rmi.Naming;
import java.rmi.NotBoundException;
import java.rmi.RMISecurityManager;
import java.rmi.RemoteException;
import java.rmi.registry.LocateRegistry;
import java.rmi.server.RMIClientSocketFactory;
import java.rmi.server.RMIServerSocketFactory;
import java.rmi.server.ServerNotActiveException;
import java.rmi.server.UnicastRemoteObject;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.Map.Entry;

import security.*;

public class RegisterServer implements IRegisterServer {

	private static final long serialVersionUID = 1L;
	
	private Map<String,String> servers; // server id, server host
	private String primaryServerID; // primary server id
	private String primaryServerAddress; // primary server address
	private long primaryServerLease;

	public RegisterServer() throws RemoteException{
		this.servers = new HashMap<String,String>();
		this.primaryServerID = null;
		this.primaryServerAddress = null;
		
		this.primaryServerLease = 0;
	}
	
	private synchronized long getLeaseValue()
	{
		return this.primaryServerLease;
	}
	
	private synchronized void setLeaseValue(long value)
	{
		this.primaryServerLease = value;
	}

	private synchronized void setPrimaryServer(String serverID, String serverAddress)
	{
		this.primaryServerID = serverID;
		this.primaryServerAddress = serverAddress;
	}
	
	private Map<String, String> updateServerList()
	{
		Iterator<Entry<String, String>> serverIterator = 
				this.servers.entrySet().iterator();
		
		while(serverIterator.hasNext())
		{
			Entry<String, String> current = serverIterator.next();
			
			String serverID = current.getKey();
			String serverAddress = current.getValue();
			try {
				IServer server = (IServer) Naming.lookup("//" + serverAddress +
						"/" + serverID);
				System.out.print("Pinging server " + serverID + " @ " +
						serverAddress + "... ");
				server.ping();
				System.out.println("up and running");
			} catch (Exception e) {
				serverIterator.remove();
				System.err.println("ERROR");
				System.err.println("Server " + serverID + " @ " +
						serverAddress + " no longer operational -- REMOVED");
			}
		}
		return this.servers;
	}

	@Override
	public String getPrimaryServerID() throws RemoteException
	{
		return this.primaryServerID;
	}

	@Override
	public String getPrimaryServerAddress() throws RemoteException
	{
		return this.primaryServerAddress;
	}

	@Override
	public boolean registerServer(String serverID) throws RemoteException{
		boolean addedServer = false;
		
		try {
			String serverAddress = UnicastRemoteObject.getClientHost();
			
			// first server, mark it as primary and start observer thread
			if(this.servers.isEmpty())
			{
				this.primaryServerID = serverID;
				this.primaryServerAddress = serverAddress;
				
				try {
					IServer primaryServer = (IServer) Naming.lookup("//" + 
							serverAddress +	"/" + serverID);
					System.out.print("Marking server " + serverID +
							" as primary... ");
					primaryServer.setPrimary();
					
					Thread observer = new Thread(new Observer(this));
					observer.start();
					
					System.out.println("SUCCESS");
				} catch (Exception e) {
					// do nothing
				}
			}
			// all servers (primary included)
			this.servers.put(serverID, serverAddress);
			addedServer = true;
			System.out.println("Registered server: " + serverID + 
					" @ " +	serverAddress);
		} catch (ServerNotActiveException e) {
			System.err.println("Couldn't retrieve host address");
		}
		return addedServer;
	}

	public static void main( String args[]) throws UnknownHostException {
		try{
			System.getProperties().put( "java.security.policy", "src/servers/policy.all");
			
			KeyManager.getInstance();
			KeyManager.makePublicCert("registerserver");
			
			if( System.getSecurityManager() == null) {
				System.setSecurityManager( new RMISecurityManager());
			}

			try { // start rmiregistry
				LocateRegistry.createRegistry( 1099);
			} catch( RemoteException e) { // if not start it
				// do nothing : already started with 'rmiregistry'
			}

			IRegisterServer registerServer = new RegisterServer();
			
			RMIClientSocketFactory csf = new CipherClientSocketFactory();
	        RMIServerSocketFactory ssf = new CipherServerSocketFactory();
	        IRegisterServer stub = (IRegisterServer) UnicastRemoteObject.exportObject(registerServer, 0, csf, ssf);
			
			Naming.rebind( "/RegisterServer", stub);
			System.out.println( "RegisterServer bound in registry");
			
		} catch( Throwable th) {
			th.printStackTrace();
		}
		
		System.out.println(java.net.InetAddress.getLocalHost());
	}

	
	@Override
	public void setLease(long value) throws RemoteException {
		this.setLeaseValue(value);
	}
	
	private static class Observer implements Runnable
	{
		private static final long SLEEP_TIME = 90000;
		
		private RegisterServer registerServer;
		
		public Observer (RegisterServer registerServer)
		{
			this.registerServer = registerServer;
		}
		
		@Override
		public void run() {
			System.out.println("Observer Thread initialized");
			for(;;)
			{
				try {
					long currentValue = registerServer.getLeaseValue();
					
					Thread.sleep(SLEEP_TIME);
					
					long newValue = registerServer.getLeaseValue();
					
					if(newValue <= currentValue)
					{
						System.err.println("Primary server lease expired");
						this.registerServer.setPrimaryServer(null, null);
						this.registerServer.setLeaseValue(0);
						
						System.out.print("Searching for a new primary server... ");
						IServer server = null;
						String serverID = null;
						String serverAddress = null;

						Iterator<Entry<String, String>> serverIterator =
								this.registerServer.updateServerList().entrySet().iterator();
						while(serverIterator.hasNext())
						{
							Entry<String, String> next = serverIterator.next();
							serverID = next.getKey();
							serverAddress = next.getValue();
							
							try {
								server = (IServer) Naming.lookup("//" +
										serverAddress + "/" + serverID);
								server.setPrimary();
								this.registerServer.setPrimaryServer(serverID, serverAddress);
								
								break;
								
							} catch (MalformedURLException e) {
								// TODO Auto-generated catch block
								e.printStackTrace();
							} catch (RemoteException e) {
								// TODO Auto-generated catch block
								e.printStackTrace();
							} catch (NotBoundException e) {
								// TODO Auto-generated catch block
								e.printStackTrace();
							}
						}
						
						if (server == null)
							System.err.println("ERROR -- No replacement servers");
						else
							System.out.println("SUCCESS -- New primary server " +
									serverID + " @ " + serverAddress);
					}
					else
						System.out.println("Primary server lease has been renewed");
					
				} catch (InterruptedException e) {
					System.err.println("Thread interrupted");
					e.printStackTrace();
				} 
			}
		}
	}
}
